package symbol;
import grammar.Lexer;
import grammar.MyError;
import java.util.ArrayList;
import java.util.Objects;

public class SymbolTable {
    public Lexer lexer;
    public SymbolBlock globalBlock;
    public SymbolBlock curBlock;
    public Function curFunction;
    public ArrayList<Function> functions = new ArrayList<>();
    private int symbolId = 0;
    public boolean addParams = false;

    public SymbolTable(Lexer lexer) {
        this.lexer = lexer;
        globalBlock = new SymbolBlock(null);
        curBlock = globalBlock;
    }

    public int getSymbolId() { return symbolId++; }

    public void addSymbolBlock() {
        SymbolBlock newBlock = new SymbolBlock(curBlock);
        curBlock.addChildBlock(newBlock);
        curBlock = newBlock;
        if (addParams) {
            Function function = functions.get(functions.size() - 1);
            function.setBlock(curBlock);
            for (Symbol parameter : function.parameters) {
                if (redefine(parameter.word.word)) {
                    lexer.addError(new MyError(parameter.word.lineNum, 'b'));
                }
                addSymbol(parameter);
            }
            addParams = false;
        }
    }

    public void subSymbolBlock(int line) {
        curBlock = curBlock.parentBlock;
        if (curBlock.parentBlock == null) {
            if (functions.size() != 0 && !functions.get(functions.size() - 1).haveReturn()) {
                lexer.addError(new MyError(line, 'g'));
            }
        }
    }

    public void addSymbol(Symbol symbol) {
        curBlock.addSymbol(symbol);
        if (curFunction != null) {
            curFunction.addSymbol(symbol);
        }
    }

    public void addFunction(Function function) {
        functions.add(function);
        curFunction = function;
    }

    public Function getFunction(String word) {
        for (Function function : functions) {
            if (Objects.equals(function.ident.word, word)) {
                return function;
            }
        }
        return null;
    }

    public boolean redefine(String name) {
        for (Symbol symbol : curBlock.symbols) {
            if (symbol.word.word.equals(name)) {
                return true;
            }
        }
        if (curBlock.parentBlock == null) {
            for (Function function : functions) {
                if (Objects.equals(function.ident.word, name)) {
                    return true;
                }
            }
        }
        return false;
    }

    public void addFunctionReturn() {
        if (curBlock.parentBlock.parentBlock == null) {
            functions.get(functions.size() - 1).setReturned(true);
        }
    }

    public boolean isSymbolConst(String name) {
        for (Symbol symbol : curBlock.symbols) {
            if (symbol.word.word.equals(name)) {
                return symbol.type.equals("const");
            }
        }
        SymbolBlock parent = curBlock.parentBlock;
        while (parent != null) {
            for (Symbol symbol : parent.symbols) {
                if (symbol.word.word.equals(name)) {
                    return symbol.type.equals("const");
                }
            }
            parent = parent.parentBlock;
        }
        return false;
    }

    public Symbol getSymbolByWord(String name) {
        for (Symbol symbol : curBlock.symbols) {
            if (symbol.word.word.equals(name)) {
                return symbol;
            }
        }
        SymbolBlock parent = curBlock.parentBlock;
        while (parent != null) {
            for (Symbol symbol : parent.symbols) {
                if (symbol.word.word.equals(name)) {
                    return symbol;
                }
            }
            parent = parent.parentBlock;
        }
        return null;
    }

    public void setLocalValueOffset(String funcName) {
        for (Function function : functions) {
            if (function.getName().equals(funcName)) {
                ArrayList<Symbol> symbols = function.getSymbols();
                for (int i = symbols.size() - 1; i >= 0; i--) {
                    symbols.get(i).setOffset_sp();
                }
            }
        }
    }

    public Symbol searchGlobalBlock(String name) {
        ArrayList<Symbol> symbols = globalBlock.symbols;
        for (int i = symbols.size() - 1; i >= 0; i--) {
            if (symbols.get(i).getName().equals(name)) {
                return symbols.get(i);
            }
        }
        return null;
    }

    public Symbol searchFuncBlock(String funcName, String name) {
        return getFunction(funcName).searchSymbol(name);
    }

    public Symbol searchFuncParam(String funcName, String val1) {
        for (Function func : functions) {
            if (func.getName().equals(funcName)) {
                return func.searchParam(val1);
            }
        }
        return null;
    }
}
